/*
 * @Author: pardon110
 * @Date: 2024-03-24 17:57:59
 * @LastEditors: pardon110@qq.com
 * @LastEditTime: 2024-03-24 18:55:09
 * @FilePath: \days7\geerpc\xclient\discovery.go
 * @Description:
 */
package xclient

import (
	"errors"
	"math"
	"math/rand"
	"sync"
	"time"
)

type SelectMode int

const (
	RandomSelect     SelectMode = iota // select randomly
	RoundRobinSelect                   // select using Robbin algorithm
)

type Discovery interface {
	Refresh() error // refresh from remote registry
	Update(servers []string) error
	Get(mode SelectMode) (string, error)
	GetAll() ([]string, error)
}

var _ Discovery = (*MultiServersDiscovery)(nil)

// MultiServersDiscovery is a discovery for multi servers without a registry center
type MultiServersDiscovery struct {
	r       *rand.Rand
	mu      sync.RWMutex
	servers []string
	index   int // record the selected position for robin algorithm
}

// 根据负载均衡策略，选择一个服务实例
func (m *MultiServersDiscovery) Get(mode SelectMode) (string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	n := len(m.servers)
	if n == 0 {
		return "", errors.New("rpc discovery: no available servers")
	}
	switch mode {
	case RandomSelect:
		return m.servers[m.r.Intn(n)], nil
	case RoundRobinSelect:
		// servers could be updated, so mode n to ensure safety
		s := m.servers[m.index%n]
		m.index = (m.index + 1) % n
		return s, nil
	default:
		return "", errors.New("rpc discovery: not supported select mode")
	}
}

// 返回所有服务实例
func (m *MultiServersDiscovery) GetAll() ([]string, error) {
	// 并发安全读
	m.mu.RLock()
	defer m.mu.RUnlock()

	servers := make([]string, len(m.servers))
	// 字节复制
	copy(servers, m.servers)
	return servers, nil
}

// 从注册中心更新服务列表
func (m *MultiServersDiscovery) Refresh() error {
	return nil
}

// 手动更新服务列表
func (m *MultiServersDiscovery) Update(servers []string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.servers = servers
	return nil
}

func NewMultiServersDiscovery(servers []string) *MultiServersDiscovery {
	d := &MultiServersDiscovery{
		servers: servers,
		r:       rand.New(rand.NewSource(time.Now().UnixNano())),
	}
	d.index = d.r.Intn(math.MaxInt32 - 1)
	return d
}
